
function range(size=10, from=0) {
  return [...Array(size)].map((_, i) => i + from);
}

Array.prototype.head = function(){
  return this[0];
}

Array.prototype.most = function(){
  return this.slice(0, -1);
}

Array.prototype.last = function(){
  return this[this.length - 1];
}

Array.prototype.rest = function(){
  return this.slice(1);
}

Array.prototype.scalarSum = function() {
  return this.reduce((acc, s) => acc + s, 0);
}

Array.prototype.mean = function() {
  return this.scalarSum() / this.length;
}

Array.prototype.stdDev = function() {
  const mean = this.mean();
  return Math.hypot(...this.map(e => e - mean));
}

Array.prototype.rotate = function(incr){
  if (incr > 0) {
      this.unshift(...this.splice(this.length - incr));
  } else {
      this.push(...this.splice(0, incr));
  }
  return this;
}

Array.prototype.diff = function (diffFunc){

  const func = diffFunc ?? ((h, t) => [h, t]);

  const diffed = [];
  for (let i = 0; i < this.length - 1; i++) {
    const pos = i === 0 ? 'HEAD' : i === this.length - 2 ? 'TAIL' : 'BODY';
    diffed.push(func(this[i], this[i+1], pos));
  }

  return diffed;
}

Array.prototype.toClosure = function() {

  if (this.isClosure()) {
    return this;
  }

  const index = this
    .diff()
    .findIndex(([head, tail]) => head === tail);

  if (index !== -1) {
    this.rotate(this.length - index);
    return this;
  }

  this.push(this.head());
  return this;
}

Array.prototype.isClosure = function() {
  return this.head() === this.last();
}

/**
 * # mitosis / 分裂数组
 * 
 * 像细胞有丝分裂一样，将一个闭合数组（首尾相同（===））按给定的切入点和穿出点
 * 裂为两个闭合数组。可选插入一个「赤道板」数组，作为两个新数组的一部分。
 * 
 * 注意，分裂过程并不复制数组元素对象，因此新形成的两个数组会共享equator中的元
 * 素对象。
 * 
 * @param {number} head entrance
 * @param {number} tail exit
 * @param {[]} equator inserted array
 */
Array.prototype.mitosis = function(head, tail, {equator=[], marker}={}) {
  
  if (head > tail) {
    [head, tail] = [tail, head];
    equator = equator.slice().reverse();
  }
  
  const orig = this
    .slice(0, head+1)
    .concat(equator)
    .concat(this.slice(tail))
    .toClosure();

  const novo = this
    .slice(head, tail+1)
    .concat(equator.slice().reverse())
    .toClosure();

  return [orig, novo];
}

Array.prototype.equal = function(func) {

  return this.map(func);

}

Array.prototype.partialOrdered = function(func) {

  return this.diff(func).every(e => e);

}

Array.prototype.insertByMap = function(map) {

  for (let i = 0; i < this.length; i++) {
    const elem = this[i];

    for (let p = 0; p < map.length; p++) {
      const [key, val] = map[p];

      if (elem === key) {
        this.splice(i + 1, 0, val);
        i++;
      }
    }
  }

  return this;
}

Array.prototype.toMap = function() {
  return Object.fromEntries(this);
}

Array.prototype.pieceWise = function(map) {
  return Object.entries(map)
    .map(([name, func]) => [name, this.filter(func)])
    .toMap()
}

Array.prototype.reverseBetween = function(head, tail, {headClose=true, tailClose=false}={}) {

  tail ??= this.length;
  head ??= 0;

  tail = Math.min(tail, this.length);
  head = Math.max(head, 0);

  head = headClose ? head : head + 1;
  tail = tailClose ? tail + 1 : tail;

  console.log('REVERSE BETWEEN', this.slice(head, tail));
  for (let i = 0; i < Math.ceil((tail - head) / 2); i++) {
    [this[head + i], this[tail - i - 1]] = [this[tail - i - 1], this[head + i]];
  }

  return this;
}

Array.prototype.rotateBetween = function(head, tail, off=1, {headClose=true, tailClose=false}={}) {
  
  tail ??= this.length;
  head ??= 0;

  tail = Math.min(tail, this.length);
  head = Math.max(head, 0);

  head = headClose ? head : head + 1;
  tail = tailClose ? tail + 1 : tail;

  off  ??= 1;
  off  = Math.max(Math.min(off > 0 ? off : (tail - head + off), tail - head - 1), 1);

  const exerpt = this.splice(head, tail - head);
  const offset = exerpt.splice(off);

  this.splice(head, 0, ...exerpt);
  this.splice(head, 0, ...offset);

  return this;
}

Array.prototype.findSuccSubs = function(headFunc, tailFunc, {headClose=false, tailClose=false}={}) {
  
  const res = [];
  
  let head = -1;
  let stat = 'FIND_HEAD';

  for (let i = 0; i < this.length; i++) {
    if (stat === 'FIND_TAIL' && tailFunc(this[i])) {
      res.push(this.slice(head + (headClose ? 0 : 1), i + (tailClose ? 1 : 0)));
      stat = 'FIND_HEAD';
    }

    if (stat === 'FIND_HEAD' && headFunc(this[i])) {
      head = i;
      stat = 'FIND_TAIL';
    }
  }

  return res;
}

Array.prototype.randomSelect = function() {
  return this[Math.floor(Math.random() * this.length)];
}